﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Packaging;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
using System.Xml.XPath;
using Duellum.Core;
using Duellum.Core.Resources;
using Duellum.Map;
using JpLabs.Geometry;
using IEnumerable = System.Collections.IEnumerable;
using Size = System.Windows.Size;
using Duellum.Wpf2d.Plugin.Drawing;
using JpLabs.Extensions;

namespace Duellum.Wpf2d.Plugin
{
	[DuelPlugin("WPF 2D Duellum Plugin")]
	public class Wpf2dPlugin : IDuelPlugin
	{
		//static string ROOT_URI = string.Format("pack://application:,,,/{0};component", typeof(Wpf2dPlugin).Assembly.GetName().Name);
		//static public Uri BuildUri(string relativeUri)
		//{
		//    return new Uri(string.Concat(ROOT_URI, relativeUri), UriKind.Absolute);
		//}

		static public readonly AugProperty HexImageProp =
			AugProperty.Create(
				"HexImage", typeof(string), typeof(Wpf2dPlugin), null
			);

		static public readonly AugProperty DrawingProp =
			AugProperty.Create(
				"Drawing", typeof(IDrawing2D), typeof(Wpf2dPlugin), null
				//TODO: OnUpdate = SetValue(HexImageProp, UseDrawing);
			);
		
		//private static Wpf2dPlugin current;
		//static public Wpf2dPlugin Current
		//{
		//    get {
		//        if (current == null) current = DuelDomain.Current.GetPlugin<Wpf2dPlugin>();
		//        return current;
		//    }
		//}

		const double SCALE_X = 19;
		const double SCALE_Y = 22;
		
		const double MAP_TO_UI_SCALE = 22;//25.3333;
		
		const double HEX_WIDTH = 26;
		const double HEX_HEIGHT = 23;

		public const string NullHexImage = "{null}";
		public const string UseDrawing = "{drawing}";
		
		private BitmapSource DefaultHexImg;
		private BitmapSource HexBorderImg;
		private BitmapSource LargeHexBorderImg;
		private BitmapSource NoFloorImg;
		
		private ImagePackageCache imageCache;
		private DuelPluginXmlConfigReader xmlConfig;

		private DuelDomain domain;
		
		public Wpf2dPlugin() {}

		public Size HexSize { get; private set; }

		static public Size DefaultHexSize { get { return new System.Windows.Size(HEX_WIDTH, HEX_HEIGHT); } }
		
		DuelDomain IDuelPlugin.Domain
		{
			get { return domain; }
			set {
				if (domain != null) throw new InvalidOperationException("Domain already set");
				if (value == null) throw new ArgumentNullException("value");
				domain = value;
			}
		}

		void ISupportInitialize.BeginInit()
		{
			xmlConfig = new DuelPluginXmlConfigReader(
				domain,
				new Uri("pack://siteoforigin:,,,/config/wpf2d.xml", UriKind.Absolute),
				typeof(Wpf2dPlugin).Namespace
			);

			//var imagePackage = Package.Open(DuelResources.MakeAbsolutePath("res\\Wpf2dImgs.dlpx"), FileMode.Open);
			var values = (IEnumerable)xmlConfig.XDoc.XPathEvaluate("/duellum/plugin/@package");
			var packUriText = values.Cast<XAttribute>().Single().Value;
			var packUri = new Uri(packUriText, UriKind.RelativeOrAbsolute);
			var imagePackage = Package.Open(DuelResources.GetStreamFromUri(packUri));
			imageCache = new ImagePackageCache(imagePackage);
			
			//TODO: use alternate images when zooming

			DefaultHexImg		= imageCache.Get("/ui/ui.fog.old.gif");
			HexBorderImg		= imageCache.Get("/ui/ui.hexborder_small.gif");
			LargeHexBorderImg	= imageCache.Get("/ui/ui.hexborder_largethin.gif");
			NoFloorImg			= imageCache.Get("/ui/ui.nofloor.gif");
			
			HexSize = DefaultHexSize; //new System.Windows.Size(HexBorderImg.PixelWidth, HexBorderImg.PixelHeight);
		}

		void ISupportInitialize.EndInit()
		{
			xmlConfig.Dispose();
			xmlConfig = null;
		}

		IEnumerable<DuelPluginName> IDuelPlugin.GetReferencedPlugins()
		{
			return DuelPluginName.FromType(typeof(CorePlugin)).ToEnumerable();
		}
		
		IEnumerable<DuelType> IDuelPlugin.GetTypes()
		{
			return xmlConfig.GetTypes();
		}

		//IEnumerable<DuelDecoration> IDuelPlugin.GetExtendedTypes()
		//{
		//    return xmlConfig.GetExtendedTypes();
		//}

		IEnumerable<DuelDecoration> IDuelPlugin.GetDecorations()
		{
			//var uri = new Uri("pack://siteoforigin:,,,/config/wpf2d.xml", UriKind.Absolute);
			//XDocument xdoc;
			//using (var xmlStream = DuelResources.GetStreamFromUri(uri)) {
			//    xdoc = XDocument.Load(XmlReader.Create(xmlStream));
			//}

			//using (var serializer = new DuelSerializer()) {
			//    serializer.Compiler.AddReferences(
			//        xdoc.XPathSelectElements("/duellum/compiler/reference").Select(x => x.Value)
			//    );
				
			//    serializer.Compiler.AddUsings(
			//        xdoc.XPathSelectElements("/duellum/compiler/using").Select(x => x.Value)
			//    );
				
			//    var types = xdoc.XPathSelectElements("/duellum/transformations/*");
			//    return (
			//        from x in types
			//        select (DuelTransformation)serializer.DeserializeDuelType(x, typeof(Wpf2dPlugin).Namespace)
			//    ).ToArray();
			//}
			return xmlConfig.GetDecorations();
		}
		
		public void Dispose()
		{
			if (xmlConfig != null) xmlConfig.Dispose();
			imageCache.Dispose();
		}
		
		public BitmapSource GetEmptyHexImg(int level, int groundLevel, double zoom)
		{
			int comp = level - groundLevel;
			if (comp == 0) return GetHexBorderImg(zoom);
			if (comp >  0) return GetNoFloorImg(zoom);
			return GetNoFloorImg(zoom); //TODO: underground image;
		}
		
		public BitmapSource GetHexBorderImg(double zoom)
		{
			return (zoom > 1) ? LargeHexBorderImg : HexBorderImg;
		}
		
		public BitmapSource GetNoFloorImg(double zoom)
		{
			return NoFloorImg;
		}

		public BitmapSource GetImage(DuelAbstraction duelObj)
		{
			var texName = duelObj.GetValue<string>(HexImageProp);
			return GetImage(texName);
		}
		
		public BitmapSource GetImage(string texName)
		{
			//var texName = duelObject.GetValue<string>(HexImageProp);
			if (texName == null) return DefaultHexImg;
			if (texName == NullHexImage) return null;
			return imageCache.Get(texName);
		}
		
		private void DrawObjs(DrawingContext dc, IEnumerable<DuelAbstraction> objects)
		{
			//TODO: greatly optimize this function
			//TODO: find a nice way to summarize objs in one position (z-order?)
			
			var rectAtOrigin = new Rect(default(Point), this.HexSize);
						
			foreach (var o in objects) {
				//TODO: cache images as drawings
				
				var tex = o.GetValue<string>(HexImageProp);
				
				if (tex != UseDrawing) {
					var img = GetImage(tex);
					if (img != null) dc.DrawImage(img, rectAtOrigin);
				} else {
					
					var drawing = o.GetValue<IDrawing2D>(DrawingProp);
					dc.PushTransform(new TranslateTransform(this.HexSize.Width / 2, this.HexSize.Height / 2));
					if (drawing != null) drawing.Render(dc);
					dc.Pop();
				}
			}
		}

		private void DrawBorder(DrawingContext dc, IEnumerable<DuelAbstraction> objects, int level, int groundLevel, double zoom)
		{
			var atOriginRect = new Rect(new Point(0, 0), this.HexSize);
			
			var borderImg
				= (objects.WhereIsGround().Any())
				? GetHexBorderImg(zoom)
				: GetEmptyHexImg(level, groundLevel, zoom);
			dc.DrawImage(borderImg, atOriginRect);
		}
		
		public ImageSource GetImage(IEnumerable<DuelAbstraction> objects, PointInt position, int groundLevel, double zoom)
		{
			var drawingGroup = new DrawingGroup();
			using (var dc = drawingGroup.Open()) {
				DrawBorder(dc, objects, position.Z, groundLevel, zoom);
				
				DrawObjs(dc, objects);
			}
			if (drawingGroup.CanFreeze) drawingGroup.Freeze();
			
			var img = new DrawingImage(drawingGroup);
			if (img.CanFreeze) img.Freeze();
			return img;
		}

		static public BoundingBoxInt GetBorder(DuelMap map, int level)
		{
			if (map == null) {
				return null;
			} else {
				var levelViewPort = new BoundingBoxInt(
					new PointInt(0, 0, level),
					new PointInt(map.DimX, map.DimY, level)
				);
				return map.Box & levelViewPort;
			}
		}
		
		static public Size GetMapSize(DuelMap map, int level)
		{
			var border = GetBorder(map, level);
			if (border == null) {
				return default(Size);
			} else {
				var x = border.Upper.X;
				var y = border.Upper.Y;
				return new Size(
				    Math.Ceiling((x + 1.3d) * SCALE_X) + 2,
				    Math.Ceiling((y + 1.5d) * SCALE_Y) + 2
				);
			}
		}

        static public Point PositionToOriginPoint(PointInt p)
        {
			//if (p.Dimensions != 2) throw new ArgumentException();
			return PositionToOriginPoint(p.X, p.Y);
        }

        static public Point PositionToOriginPoint(int x, int y)
        {
			const double HALF_Y = .5;
			return new Point(
			    x * SCALE_X,
			    (y + ((x % 2) * HALF_Y)) * SCALE_Y //((x % 2 == 1) ? y + HALF_Y : y) * SCALE_Y
			);
        }

        static public Point ScalePointToMapScale(Point p)
        {
			return p.Scale(1 / MAP_TO_UI_SCALE);
        }

        static public Point PositionToCenterPoint(PointInt pos)
        {
			return PositionToCenterPoint(pos.X, pos.Y);
        }

        static public Point PositionToCenterPoint(int x, int y)
        {
			return PositionToOriginPoint(x, y).Translate(HEX_WIDTH / 2, HEX_HEIGHT / 2);
        }

		static public PointInt PointToPosition(Point p)
		{
			//Inspired by http://www-cs-students.stanford.edu/~amitp/Articles/GridToHex.html
			
			p.X /= SCALE_X;
			p.Y /= SCALE_Y;
			
			var col = (int)Math.Floor(p.X);
			var colDiv3 = (int)Math.Floor(p.X * 3);
			var rowDiv2 = (int)Math.Floor(p.Y * 2);
			
			if (colDiv3 % 3 == 0) {
				var upperDiag = (rowDiv2 + col) % 2 == 0;
				var colRemainder = p.X * 3 - colDiv3;
				var rowRemainder = p.Y * 2 - rowDiv2;
				if (!upperDiag) rowRemainder = 1 - rowRemainder;
				
				if (colRemainder + rowRemainder < 1) col--;
			}
			
			var row = (rowDiv2 - (col % 2)) / 2;
			return new PointInt(col,  row);
		}
	}
}
